package com.nekonomics
package domain.service

import api.dto.response.{OrderDetailDto, OrderSummaryDto, ProductDto, TopNDto}
import domain.entity.Merchant
import error.{MerchantError, OrderError, PermissionError, UserError}
import persistence.query.{OrderDetailRepo, OrderItemDetailRepo, OrderSummaryRepo, SkuDetailRepo, TopNRepo}
import persistence.repository.{MerchantRepo, OrderRepo, UserRepo}
import utils.DoobieUtil.*

import cats.syntax.traverse.*
import com.nekonomics.types.OrderStatus.Shipped
import doobie.hikari.HikariTransactor
import zio.*

trait MerchantService {
  def findBySpuId(spuId: Long): DomainIO[Merchant]

  def findByUserId(userId: Long): DomainIO[Merchant]

  def listOrders(merchantId: Long): DomainIO[List[OrderDetailDto]]

  def listProducts(merchantId: Long): DomainIO[List[ProductDto]]
  
  def shipOrder(orderId: Long): DomainIO[Unit]

  def weeklyOrderSummary(merchantId: Long): DomainIO[List[OrderSummaryDto]]

  def top10Products(merchantId: Long): DomainIO[List[TopNDto]]
}

object MerchantService {
  val live: TaskLayer[MerchantService] = (config.layers ++ persistence.layers) >>> ZLayer.fromFunction(MerchantServiceLive.apply)

}


case class MerchantServiceLive(
  xa: HikariTransactor[Task],
  merchantRepo: MerchantRepo,
  userRepo: UserRepo,
  orderDetailRepo: OrderDetailRepo,
  orderItemDetailRepo: OrderItemDetailRepo,
  skuDetailRepo: SkuDetailRepo,
  orderRepo: OrderRepo,
  orderSummaryRepo: OrderSummaryRepo,
  topNRepo: TopNRepo
) extends MerchantService {
  override def findBySpuId(spuId: Long): DomainIO[Merchant] = {
    merchantRepo.findBySpuId(spuId)
      .convertWithXa(xa)
      .someOrFail(MerchantError.MerchantNotFound)
  }


  override def findByUserId(userId: Long): DomainIO[Merchant] = {
    merchantRepo
      .findByUserId(userId)
      .convertWithXa(xa)
      .someOrFail(PermissionError.PermissionDenied)
  }


  override def listOrders(merchantId: Long): DomainIO[List[OrderDetailDto]] = {
    val tx = for {
      orderDetails <- orderDetailRepo.listByMerchantId(merchantId)
      orderDetailDtoList <- orderDetails.traverse(orderDetail =>
        orderItemDetailRepo.listByOrderId(orderDetail.id)
          .map(orderItemDetails =>
            OrderDetailDto.of(orderDetail, orderItemDetails)
          )
      )
    } yield orderDetailDtoList.sorted
    tx.convertWithXa(xa)
  }


  override def listProducts(merchantId: Long): DomainIO[List[ProductDto]] = {
    skuDetailRepo
      .listByMerchantId(merchantId)
      .convertWithXa(xa)
      .map(_.map(ProductDto.of))
  }


  override def shipOrder(orderId: Long): DomainIO[Unit] = {
    orderRepo
      .updateStatusById(orderId, Shipped)
      .convertWithXa(xa)
      .flatMap(affected => 
        if affected == 0 then ZIO.fail(OrderError.OrderNotFound) 
        else ZIO.unit
      )
  }


  override def weeklyOrderSummary(merchantId: Long): DomainIO[List[OrderSummaryDto]] = {
    orderSummaryRepo
      .weeklySummary(merchantId)
      .map(_.map(OrderSummaryDto.of).sorted(using summon[Ordering[OrderSummaryDto]].reverse))
      .convertWithXa(xa)
  }


  override def top10Products(merchantId: Long): DomainIO[List[TopNDto]] = {
    topNRepo
      .top10daily( merchantId)
      .map(_.map(TopNDto.of).sorted)
      .convertWithXa(xa)

  }
}
